/**
 * Copyright (C) 2009 Noel Ang
 *
 * This file is part of Samsara.
 *
 * Samsara is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Samsara is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Samsara.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package com.lunarshades.samsara.ui.actions;

import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

import com.lunarshades.samsara.Karma;
import com.lunarshades.samsara.Raider;
import com.lunarshades.samsara.ui.RosterTableModel;
import com.lunarshades.samsara.ui.SamsaraFrame;
import com.lunarshades.samsara.ui.Utils;

/**
 * Action responsible for importing external data into a RosterTableModel.
 *
 * @author Noel Ang, sparta.variable@gmail.com
 * @see com.lunarshades.samsara.ui.RosterTableModel
 */
public class RosterImportAction<E extends Raider> extends AbstractAction {

public RosterImportAction(RosterTableModel<E> model,
                          Class<E> elementClass,
                          SaveDataAction pSaveAction,
                          SamsaraFrame pAppFrame
) {
    super(NAME);

    mApp = pAppFrame;
    mModel = model;
    mElementClass = elementClass;
    mSaveAction = pSaveAction;

    putValue(Action.MNEMONIC_KEY, MNEMONIC);
    putValue(Action.SHORT_DESCRIPTION, SHORT_TEXT);
}

private boolean _import() {
    boolean success = false;

    String data = prompt();
    if (data != null && !"".equals(data.trim())) {
        success = parse(new StringReader(data));
    }
    return success;
}

private boolean parse(Reader reader) {
    boolean success = true;
    BufferedReader in = new BufferedReader(reader);
    List<E> allParsed = new LinkedList<E>();
    String line;
    try {
        while ((line = in.readLine()) != null) {
            line = line.trim();
            if (!"".equals(line)) {
                List<E> parsed = parseLine(line);
                allParsed.addAll(parsed);
                if (parsed.isEmpty()) {
                    success = false;
                    break;
                }
            }
        }
    } catch (IOException e) {
        e.printStackTrace(System.err);
        StringBuffer buffer = new StringBuffer();
        buffer.append("Access failed. ");
        buffer.append(e.getLocalizedMessage());
        buffer.append("\n");
        showErrorDialog(buffer.toString());
        success = false;
    }

    if (!success) {
        allParsed.clear();
    } else {
        List<Raider> uniqueParsed = new ArrayList<Raider>(allParsed.size());
        for (Raider raider : allParsed) {
            if (!uniqueParsed.contains(raider)) {
                uniqueParsed.add(raider);
            } else {
                showErrorDialog(
                        "Import contains more than one record for '"
                                + raider.name()
                                + "'."
                );
                success = false;
                break;
            }
        }
        uniqueParsed.clear();

        if (success) {
            synchronized (mModel) {
                int rowCount = mModel.getRowCount();
                for (int i = 0; i < rowCount; ++i) {
                    try {
                        mModel.setRowAt(null, 0);
                    } catch (Exception e) {
                        assert false;
                    }
                }
                for (E raider : allParsed) {
                    try {
                        mModel.setRowAt(raider, mModel.getRowCount());
                    } catch (Exception e) {
                        assert false;
                    }
                }
            }
        }
    }

    return success;
}

private List<E> parseLine(String pLine) {
    assert pLine != null;

    boolean success = false;
    List<E> tableBuffer = new LinkedList<E>();
    StringTokenizer tokenizer = new StringTokenizer(pLine, ",");
    int numTokens = tokenizer.countTokens();

    if (numTokens > 0) {
        if (numTokens % 2 != 0) {
            StringBuffer buffer = new StringBuffer();
            buffer.append("Bad format: Found non-pair in content:\n");
            buffer.append(pLine);
            showErrorDialog(buffer.toString());
        } else {
            while (tokenizer.hasMoreTokens()) {
                E raider;
                try {
                    raider = mElementClass.newInstance();
                } catch (Exception e) {
                    e.printStackTrace(System.err);
                    StringBuffer buffer = new StringBuffer();
                    buffer.append("Import canceled!\n");
                    buffer.append(e.getClass().getName()).append('\n');
                    buffer.append(e.getLocalizedMessage());
                    showErrorDialog(buffer.toString());
                    break;
                }

                // Raider name(s)
                StringTokenizer nameTokenizer =
                        new StringTokenizer(tokenizer.nextToken(), "/");
                while (nameTokenizer.hasMoreTokens()) {
                    raider.name(nameTokenizer.nextToken());
                }

                // Karma
                String token = tokenizer.nextToken();
                try {
                    int points = Utils.makeInt(token);
                    raider.karma(new Karma(points));
                } catch (NumberFormatException e) {
                    StringBuffer buffer = new StringBuffer();
                    buffer.append("Read: '").append(token).append("'\n");
                    buffer.append("from import while expecting a number.");
                    showErrorDialog(buffer.toString());
                    break;
                }

                if (tableBuffer.contains(raider)) {
                    StringBuffer buffer = new StringBuffer();
                    buffer.append("Import contains more than one record for '");
                    buffer.append(raider.name()).append("'.");
                    showErrorDialog(buffer.toString());
                    break;
                }
                
                tableBuffer.add(raider);
                success = true;
            }
        }

        if (!success) {
            tableBuffer.clear();
        }
    }
    return tableBuffer;
}

private void showErrorDialog(String pMessage) {
    JOptionPane.showMessageDialog(mApp,
            pMessage,
            "Import canceled",
            JOptionPane.ERROR_MESSAGE
    );
}

private String prompt() {

    String promptInput = null;

    if (mScrollPane == null) {
        Font font = Font.decode(Font.DIALOG).deriveFont(Font.PLAIN, 10.0F);
        mTextArea = new JTextArea();
        mTextArea.setEditable(true);
        mTextArea.setEnabled(true);
        mTextArea.setFocusable(true);
        mTextArea.setLineWrap(true);
        mTextArea.setWrapStyleWord(true);
        mTextArea.setFont(font);
        mTextArea.setRows(ROWS);
        mScrollPane = new JScrollPane(mTextArea,
                JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED
        );
    }

    int decision = JOptionPane.showConfirmDialog(mApp,
            mScrollPane,
            INPUT_TITLE,
            JOptionPane.OK_CANCEL_OPTION,
            JOptionPane.PLAIN_MESSAGE
    );

    if (decision == JOptionPane.OK_OPTION) {
        promptInput = mTextArea.getText();
    }

    return promptInput;
}

public void actionPerformed(ActionEvent ignored) {
    if (!mApp.isDataSaved()) {
        int decision = mApp.promptToSaveData();
        switch (decision) {
        case JOptionPane.NO_OPTION:
            // Don't save
            break;
        case JOptionPane.YES_OPTION:
            // Save
            if (mSaveAction.execute()) {
                break;
            }
            // fall-through
        default:
            // Cancel load.
            return;
        }
    }
    if (_import()) {
        mApp.postStatusMessage(SUCCESS_STATUS);
        firePropertyChange(DATA_IMPORTED, null, null);
    } else {
        mApp.postStatusMessage(FAILED_STATUS);
    }
}

private static final String NAME = "Comma-delimited";
private static final String SHORT_TEXT =
        "Import data from a comma-delimited form";
private static final String INPUT_TITLE = "Enter Comma-delimited Data:";
private static final String SUCCESS_STATUS = "Successful import.";
private static final String FAILED_STATUS = "Import canceled.";
private static final int MNEMONIC = KeyEvent.VK_C;
private static final int ROWS = 28;
private final SamsaraFrame mApp;
private final RosterTableModel<E> mModel;
private final Class<E> mElementClass;
private volatile JTextArea mTextArea;
private volatile JScrollPane mScrollPane;
private final SaveDataAction mSaveAction;

public static final String DATA_IMPORTED = "DataImported";
}
